package org.simplesocket.simplenettymtbatis.config;

import cn.hutool.core.util.StrUtil;
import com.alibaba.druid.pool.DruidDataSource;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.simplesocket.simplenettycore.context.SimpleNettyContext;

import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

@Slf4j
@Data
public class MybatisConfig {
    
    private Class<?> driver = com.mysql.cj.jdbc.Driver.class;
    
    private String url;
    
    private String username;
    
    private String password;
    
    private String mapperInterfacePackage = "mapper";
    
    private String mapperXMLPackage = "mapper";
    
    private List<Class<?>> mappers = new ArrayList<>();
    
    private MybatisConfig() {}
    
    public SqlSessionFactory getSqlSessionFactory(SimpleNettyContext simpleNettyContext) {
        validateConfig();// 校验配置先
        
        // 1. 创建 DataSource
        DruidDataSource datasource = new DruidDataSource();
        datasource.setDriverClassName(driver.getTypeName());
        datasource.setUrl(url);
        datasource.setUsername(username);
        datasource.setPassword(password);
        
        // 2. 创建事务工厂
        JdbcTransactionFactory transactionFactory = new JdbcTransactionFactory();
        
        // 3. 创建环境配置
        Environment environment = new Environment.Builder("development")
                .transactionFactory(transactionFactory)
                .dataSource(datasource)
                .build();
        
        // 4. 创建核心配置对象
        Configuration configuration = new Configuration(environment);
        
        // 5. 添加 Mapper
        if (!mappers.isEmpty()){
            // 单独添加
            for (Class<?> mapper : mappers) {
                configuration.addMapper(mapper);
                log.info("Successful to add mapper interface: {}", mapper.getName());
            }
        }else {
            String pkg = simpleNettyContext.getSimpleNettyConfig().getBasePackageName()
                    + "." + mapperInterfacePackage;
            configuration.addMappers(pkg); // 指定mapper包路径
            log.info("Successful to add mapper package '{}'", pkg);
        }
        
        // 获取所有 `mapper.xml` 文件路径
        URI uri = getMapperXMLUri();
        
        try (Stream<Path> paths = Files.walk(Paths.get(uri))) {
            paths.filter(Files::isRegularFile)
                    .filter(path -> path.toString().endsWith(".xml"))
                    .forEach(path -> {
                        try (InputStream inputStream = Files.newInputStream(path)) {
                            // 使用 XMLMapperBuilder 解析每个 `mapper.xml`
                            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(
                                    inputStream,
                                    configuration,
                                    path.toString(),
                                    configuration.getSqlFragments()
                            );
                            xmlMapperBuilder.parse();
                            log.info("Successful to load mapper xml '{}'", path.getFileName());
                        } catch (Exception e) {
                            throw new RuntimeException("Failed to parse mapper XML: " + path, e);
                        }
                    });
        }catch (Exception e){
            throw new RuntimeException(e);
        }
        
        // 6. 构建 SqlSessionFactory
        return new SqlSessionFactoryBuilder().build(configuration);
    }
    
    private URI getMapperXMLUri() {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        if (classLoader == null) {
            throw new RuntimeException("Class loader not available");
        }
        URI uri;
        try {
            URL resourceUri = classLoader.getResource(mapperXMLPackage);
            if (resourceUri == null) {
                throw new RuntimeException("Can not find mapper xml file directory: "
                        + mapperXMLPackage + " in resources directory");
            }
            uri = resourceUri.toURI();
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
        return uri;
    }
    
    private void validateConfig(){
        if (StrUtil.isBlank(url) || StrUtil.isBlank(username) || StrUtil.isBlank(password)){
            throw new RuntimeException("Please configure 'url' 'username' 'password' when you use config object");
        }
        
        /*
        添加接口包名扫描全部接口，还是直接添加接口二选一，不管选择哪个，都要在resources下建立一个与mapper接口相同包名的目录，
        并且xml的名字要和mapper接口一致，例如我有一个mapper包 mybatis，其中有一个mapper接口 TestMapper，那么在resources
        下也要建立一个文件夹 mybatis，也要有一个TestMapper.xml文件，否则会报错
         */
        if (StrUtil.isBlank(mapperInterfacePackage) && (mappers == null || mappers.isEmpty())){
            throw new RuntimeException("'mapperPackageName' or 'mapper',you must configure one");
        }
    }
    
    public static Builder builder() {
        return new Builder();
    }
    
    public static class Builder{
        
        private final MybatisConfig config;
        
        private Builder() {
            this.config = new MybatisConfig();
        }
        
        public Builder driver(Class<?> driver){
            this.config.driver = driver;
            return this;
        }
        
        public Builder url(String url){
            this.config.setUrl(url);
            return this;
        }
        
        public Builder username(String username){
            this.config.setUsername(username);
            return this;
        }
        
        public Builder password(String password){
            this.config.setPassword(password);
            return this;
        }
        
        public Builder mapperInterfacePackage(String packageName){
            this.config.setMapperInterfacePackage(packageName);
            return this;
        }
        
        public Builder mapperXMLPackage(String packageName){
            this.config.setMapperXMLPackage(packageName);
            return this;
        }
        
        public Builder mapper(Class<?> mapper){
            if (mapper != null){
                this.config.mappers.add(mapper);
            }
            return this;
        }
        
        public MybatisConfig build(){
            return config;
        }
    }
    
}